<template>
  <uploader
    ref="uploader"
    :options="options"
    :auto-start="false"
    class="uploader-app"
    @file-added="onFileAdded"
  >
    <uploader-unsupport />
    <uploader-drop>
      <p>拖动文件</p>
      <uploader-btn>选择文件</uploader-btn>
    </uploader-drop>
    <uploader-list />
    <p>文件处理状态：{{ status }}</p>
    <p>文件上传进度：{{ progress }}%</p>
  </uploader>
</template>

<script>
import axios from 'axios'
import SparkMD5 from 'spark-md5'
import qs from 'qs'
import { getChunks, newMultipart, getMultipartUrl, updateChunk, completeMultipart } from '@/api/upload'

export default {
  components: {

  },
  data() {
    return {
      options: {
        // 分片大小
        chunkSize: 5 * 1024 * 1024
      },
      progress: 0,
      status: '初始状态',
      urlPrex: 'http://192.168.31.60.34:39988/minio'
    }
  },
  created() {
    // const uploaderInstance = this.$refs.uploader;
    console.log('created')
  },
  mounted() {
    this.$nextTick(() => {
      window.uploader = this.$refs.uploader.uploader
    })
  },
  methods: {
    onFileAdded(file) {
      this.progress = 0
      this.status = '初始状态'
      file.urlPrex = this.urlPrex
      // 计算MD5
      this.computeMD5(file)
    },
    getSuccessChunks(file) {
      return new Promise((resolve, reject) => {
        getChunks({ md5: file.uniqueIdentifier }).then(function(response) {
          file.uploadId = response.result.uploadId
          file.uuid = response.result.uuid
          file.uploaded = response.result.uploaded
          file.chunks = response.result.chunks
          resolve(response)
        }).catch(function(error) {
          console.log(error)
          reject(error)
        })
      })
    },
    newMultiUpload(file) {
      return new Promise((resolve, reject) => {
        newMultipart({
          totalChunkCounts: file.totalChunkCounts,
          md5: file.uniqueIdentifier,
          size: file.size,
          fileName: file.name
        }).then(function(response) {
          file.uploadId = response.result.uploadId
          file.uuid = response.result.uuid
          file.objectName = response.result.objectName
          resolve(response)
        }).catch(function(error) {
          console.error(error)
          reject(error)
        })
      })
    },
    // 上传到minio
    multipartUpload(file) {
      const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
      const chunkSize = 1024 * 1024 * 64
      // 总分片数量
      const chunks = Math.ceil(file.size / chunkSize)
      let currentChunk = 0
      const fileReader = new FileReader()
      const time = new Date().getTime()

      /**
       * 获取下一个分片流到上传的分片
       */
      function loadNext() {
        const start = currentChunk * chunkSize
        const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize

        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
      }

      /**
       * 是否
       * @returns {boolean}
       */
      function checkSuccessChunks() {
        var index = successChunks.indexOf((currentChunk + 1).toString())
        return index !== -1
      }

      function getUploadChunkUrl(currentChunk, partSize) {
        return new Promise((resolve, reject) => {
          getMultipartUrl({
            objectName: file.objectName,
            uploadId: file.uploadId,
            size: partSize,
            chunkNumber: currentChunk + 1
          }).then(function(response) {
            urls[currentChunk] = response.result.url
            resolve(response)
          }).catch(function(error) {
            console.log(error)
            reject(error)
          })
        })
      }

      function uploadMinio(url, e) {
        return new Promise((resolve, reject) => {
          axios.put(url, e.target.result
          ).then(function(res) {
            etags[currentChunk] = res.headers.etag
            resolve(res)
          }).catch(function(err) {
            console.log(err)
            reject(err)
          })
        })
      }

      function updateChunkFunc(currentChunk) {
        return new Promise((resolve, reject) => {
          updateChunk(qs.stringify({
            uuid: file.uuid,
            chunkNumber: currentChunk + 1,
            etag: etags[currentChunk]
          })).then(function(response) {
            resolve(response)
          }).catch(function(error) {
            console.log(error)
            reject(error)
          })
        })
      }

      async function uploadChunk(e) {
        if (!checkSuccessChunks()) {
          const start = currentChunk * chunkSize
          const partSize = ((start + chunkSize) >= file.size) ? file.size - start : chunkSize

          // 获取分片上传url
          await getUploadChunkUrl(currentChunk, partSize)
          if (urls[currentChunk] !== '') {
            // 上传到minio
            await uploadMinio(urls[currentChunk], e)
            if (etags[currentChunk] !== '') {
              // 更新数据库：分片上传结果
              // await updateChunkFunc(currentChunk);
            }
          }
        }
      }

      function completeUpload() {
        return new Promise((resolve, reject) => {
          completeMultipart({
            uuid: file.uuid,
            uploadId: file.uploadId,
            objectName: file.objectName,
            size: file.size
          }).then(function(response) {
            resolve(response)
          }).catch(function(error) {
            console.log(error)
            reject(error)
          })
        })
      }

      var successChunks = []
      var successParts = []
      successParts = file.chunks.split(',')
      for (let i = 0; i < successParts.length; i++) {
        successChunks[i] = successParts[i].split('-')[0]
      }

      var urls = []
      var etags = []

      console.log('上传分片...')
      this.status = '上传中'
      loadNext()
      fileReader.onload = async(e) => {
        await uploadChunk(e)
        currentChunk++

        if (currentChunk < chunks) {
          console.log(`第${currentChunk}个分片上传完成, 开始第${currentChunk + 1}/${chunks}个分片上传`)
          this.progress = Math.ceil((currentChunk / chunks) * 100)
          await loadNext()
        } else {
          await completeUpload()
          console.log(`文件上传完成：${file.name} \n分片：${chunks} 大小:${file.size} 用时：${(new Date().getTime() - time) / 1000} s`)
          this.progress = 100
          this.status = '上传完成'
          // window.location.reload();
        }
      }
    },
    // 计算MD5
    computeMD5(file) {
      const blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice
      const chunkSize = 1024 * 128
      const chunks = Math.ceil(file.size / chunkSize)
      let currentChunk = 0
      const spark = new SparkMD5.ArrayBuffer()
      const fileReader = new FileReader()

      const time = new Date().getTime()

      console.log('计算MD5...')
      this.status = '计算MD5'
      file.totalChunkCounts = chunks
      loadNext()

      fileReader.onload = (e) => {
        spark.append(e.target.result) // Append array buffer
        currentChunk++

        if (currentChunk < chunks) {
          console.log(`第${currentChunk}分片解析完成, 开始第${currentChunk + 1}/${chunks}分片解析`)
          loadNext()
        } else {
          const md5 = spark.end()
          console.log(`MD5计算完成：${file.name} \nMD5：${md5} \n分片：${chunks} 大小:${file.size} 用时：${(new Date().getTime() - time) / 1000} s`)
          spark.destroy() // 释放缓存
          file.uniqueIdentifier = md5 // 将文件md5赋值给文件唯一标识
          file.cmd5 = false // 取消计算md5状态
          // append: urlPrex 请求地址 totalChunkCounts; 分片数量 uniqueIdentifier md5; cmd5 取消计算md5状态
          this.computeMD5Success(file)
        }
      }

      fileReader.onerror = () => {
        console.warn('oops, something went wrong.')
        file.cancel()
      }

      function loadNext() {
        const start = currentChunk * chunkSize
        const end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize

        fileReader.readAsArrayBuffer(blobSlice.call(file.file, start, end))
      }
    },
    async computeMD5Success(file) {
      // 根据MD5获取此文件在服务器上0.成功的分片
      await this.getSuccessChunks(file)

      if (!file.uploadId || !file.uuid) { // 未上传过
        await this.newMultiUpload(file)
        if (file.uploadId && file.uuid) {
          // 文件已在服务器上传，重置上传成功的分片编号
          file.chunks = ''
          this.multipartUpload(file)
        } else {
          // 失败如何处理
          return
        }
      } else {
        if (file.uploaded) { // 已上传成功
          // 秒传，数据库数据作为上传数据即可
          console.log('文件已上传完成')
          this.progress = 100
          this.status = '上传完成'
          // window.location.reload();
        } else {
          // 断点续传
          this.multipartUpload(file)
        }
      }

      function addAttachment(file) {
        return new Promise((resolve, reject) => {
          axios.post(file.urlPrex + '/add', qs.stringify({
            uuid: file.uuid,
            file_name: file.name,
            size: file.size
          })).then(function(response) {
            resolve(response)
          }).catch(function(error) {
            console.log(error)
            reject(error)
          })
        })
      }
    }
  }
}
</script>

<style>
.uploader-app {
  width: 850px;
  padding: 15px;
  margin: 40px auto 0;
  font-size: 12px;
  box-shadow: 0 0 10px rgba(0, 0, 0, .4);
}

.uploader-app .uploader-btn {
  margin-right: 40px;
}

.uploader-app .uploader-list {
  max-height: 440px;
  overflow: auto;
  overflow-x: hidden;
  overflow-y: auto;
}
</style>
